/**
 * File: bootAnim.cpp
 *
 * Author: Al Chaussee
 * Created: 07/27/2018
 *
 * Description: Draws the boot animation to the screen forever until stopped
 *              Leaves the screen on when stopped under the assumption whatever is stopping
 *              it will continue to use the screen
 *
 * Copyright: Anki, Inc. 2018
 *
 **/

#include "core/lcd.h"

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <chrono>
#include <algorithm>
#include <signal.h>

#define FRAME_WIDTH  184 
#define FRAME_HEIGHT 96
#define NUM_BYTES_PER_FRAME (FRAME_WIDTH * FRAME_HEIGHT * sizeof(uint16_t))

namespace {
  // Path to the boot animation file generated by `python3 /robot/test/gif_to_raw.py <gif of boot animation>`
  // It is a binary file containing all the frames in the animation in rgb565 format
  static const char* _animPath = "/anki/data/assets/cozmo_resources/config/engine/animations/boot_anim.raw";
  bool gShutdown = false;
}

extern "C" void core_common_on_exit(void)
{
  lcd_shutdown();
}

static void animate(void* frame)
{
  lcd_draw_frame2((uint16_t*)frame, FRAME_WIDTH * FRAME_HEIGHT * sizeof(uint16_t));
}

static void handler(int signum)
{
  gShutdown = true;
}

int main(int argc, char** argv)
{
  // Setup signal handlers so we can cleanly exit
  signal(SIGTERM, handler);
  signal(SIGINT,  handler);

  // Init lcd
  int rc = lcd_init();
  if (rc != 0)
  {
    printf("Failed to init lcd\n");
    return rc;
  }

  // Open animation file for reading
  int fd = open(_animPath, O_RDONLY);
  if(fd < 0)
  {
    printf("Failed to open %s\n", _animPath);
    return -1;
  }

  // Get the length of the file to determine how long the animation is
  off_t len = lseek(fd, 0, SEEK_END);
  if(len == -1)
  {
    printf("lseek failed\n");
    return -1;
  }
  static const uint32_t kTotalNumFrames = len / NUM_BYTES_PER_FRAME;

  // Memory map the anim file
  void* anim = mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
  if(anim == nullptr)
  {
    printf("mmap failed\n");
    return -1;
  }

  // Close the fd after memory mapping
  rc = close(fd);
  if(rc != 0)
  {
    printf("close failed\n");
  }  

  // Start drawing the boot animation to the screen

  // Keep track of how far in time we are in this loop of the animation
  // since drawing the frame takes longer than the frame should actually
  // be displayed (~41ms to draw vs a desired frame rate of 24fps)
  // we need to skip frames
  uint32_t timeCount = 0;
  
  while (!gShutdown)
  {
    // Figure out which frame we should play in order to adhere to
    // a frame rate of kFrameDuration_ms
    static const uint32_t kFrameDuration_ms = 41;
    const uint32_t nextFrameToDraw = timeCount / kFrameDuration_ms;

    // Time how long it takes to animate/draw this frame
    const auto startTime = std::chrono::steady_clock::now();
    animate(((uint8_t*)anim) + (nextFrameToDraw * NUM_BYTES_PER_FRAME));
    const auto endTime = std::chrono::steady_clock::now();

    const auto dif = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
    timeCount += dif.count();

    // Role timeCount over if it exceeds the total duration of the animation
    if(timeCount >= ((kTotalNumFrames-1)*kFrameDuration_ms))
    {
      timeCount %= kTotalNumFrames * kFrameDuration_ms;
    }
  }

  // Unmap the anim
  rc = munmap(anim, len);
  if(rc != 0)
  {
    printf("munmap failed\n");
  }

  lcd_shutdown();
  return 0;
}

